package carousel;

import java.io.File;
import java.nio.ByteBuffer;

import javax.media.opengl.GL;
//import javax.media.opengl.GL2;
import javax.media.opengl.GL2ES2;
import javax.media.opengl.GLAutoDrawable;
import javax.media.opengl.GLCapabilities;
import javax.media.opengl.GLEventListener;
import javax.media.opengl.GLProfile;
import javax.media.opengl.awt.GLCanvas;
import javax.swing.JFrame;

import tutorial06.utils.Buffers;
import tutorial06.utils.ModelViewMatrix;
import tutorial06.utils.Shader;

import com.jogamp.opengl.util.FPSAnimator;
import com.jogamp.opengl.util.texture.Texture;
import com.jogamp.opengl.util.texture.TextureIO;

public class Carousel {

	public static void main(String[] args) {
		
		JFrame frame=new JFrame();
		
		frame.setSize(600,600);
		frame.setTitle("Hello Universe");

		//Costruisco un GLEventListener
		GraphicsListener listener=new GraphicsListener();
		//Costruisco un GLCanvas
		GLProfile profile=GLProfile.getGL2ES2();
		GLCapabilities capabilities = new GLCapabilities(profile);
		capabilities.setSampleBuffers(true);
		capabilities.setNumSamples(8);
		
		GLCanvas canvas = new GLCanvas();
		//Associo il listener al canvas
		canvas.addGLEventListener(listener);
		//Il canvas � un componente grafico!! Posso aggiungerlo al Frame
		frame.getContentPane().add(canvas);
		FPSAnimator an = new FPSAnimator(canvas,60);
		an.start();
		
		frame.setDefaultCloseOperation(JFrame.EXIT_ON_CLOSE);
		frame.setVisible(true);
	}
	
	public static class GraphicsListener implements GLEventListener{
		
		private static final int STEPS = 100;
		private static final float passo = (float)1 / STEPS;
		
		private static short[] indices;
		
		private static final int NUMBER_OF_POLES=14;

		/*
		 * parametrizzazione sfera:
		 * 	teta in [0, 2 * PI]
		 * 	phi in [0, PI]
		 * 	(r cos(teta) sen(phi), r sen(teta) sen(phi), r cos(phi)
		 */

		private static String vertexShaderDisk="" +
				"uniform mat4 transform;" +
				"uniform mat4 lightTransform;" +
				"uniform vec3 lightPosition;" +
				"attribute vec2 position;" +
				"attribute vec3 normal;" +
				"varying vec2 txCoord;" + //add
				"varying float distance;" +
				"varying vec3 N;" +
				"varying vec3 V;" +
				"varying vec3 L;" +
				
				"void main(){" +
				"	vec3 point = vec3(position.x * cos(position.y * 6.29), 0.0, position.x * sin(position.y * 6.29));" +
				"	vec4 vPosition = transform*vec4(point, 1.0);" +
				"	N = normalize((transform * vec4(0.0, 1.0, 0.0, 0.0)).xyz);" +
				"	vec3 transformedLightPos = (lightTransform * vec4(lightPosition, 0.0)).xyz;" +
				"	L = (transformedLightPos - vPosition.xyz);" + 
				"	distance = length(L);" +
				"	L = normalize(L);" +
				"	V = vec3(0.0, 0.0, 1.0);" + 
				"	gl_Position = vPosition;" +
				"	txCoord.x=position.y; " +
				"	txCoord.y=position.x; " +
				"}";

		private static String vertexShaderCilinder="" +
				"uniform mat4 transform;" +
				"uniform mat4 lightTransform;" +
				"uniform vec3 lightPosition;" +
				"attribute vec2 position;" +
				"attribute vec3 normal;" +
				"varying vec2 txCoord;" + //add
				
				"varying float distance;" +
				"varying vec3 N;" +
				"varying vec3 V;" +
				"varying vec3 L;" +
				
				"void main(){" +
				"	vec3 point = vec3(cos(position.y * 6.29), position.x, sin(position.y * 6.29));" +
 				"	vec4 vPosition = transform*vec4(point, 1.0);" +
				"	N = normalize((transform*vec4(point.x, 0.0, point.z, 0.0)).xyz);" +
				"	vec3 transformedLightPos = (lightTransform * vec4(lightPosition, 0.0)).xyz;" +
				"	L = (transformedLightPos - vPosition.xyz);" + 
				"	distance = length(L);" +
				"	L = normalize(L);" +
				"	V = vec3(0.0, 0.0, 1.0);" + 
				"	gl_Position = vPosition;" +
				"	txCoord.x=position.y; " +
				"	txCoord.y=position.x; " +
				"}";

		private static String vertexShaderSphere="" +
				"uniform mat4 transform;" +
				"uniform mat4 lightTransform;" +
				"uniform vec3 lightPosition;" +
				"attribute vec2 position;" +
				"attribute vec3 normal;" +
				"varying vec2 txCoord;" + //add
				
				"varying float distance;" +
				"varying vec3 N;" +
				"varying vec3 V;" +
				"varying vec3 L;" +
				
				"void main(){" +
				"	vec3 point = vec3(cos(position.y * 6.29) * sin(position.x * 3.15), -cos(position.x * 3.15), sin(position.y * 6.29) * sin(position.x * 3.15));" +
				"	vec4 vPosition = transform*vec4(point, 1.0);" +
				"	N = normalize((transform*vec4(point, 0.0)).xyz);" +
				"	vec3 transformedLightPos = (lightTransform * vec4(lightPosition, 0.0)).xyz;" +
				"	L = (transformedLightPos - vPosition.xyz);" + 
				"	distance = length(L);" +
				"	L = normalize(L);" +
				"	V = vec3(0.0, 0.0, 1.0);" + 
				"	gl_Position = vPosition;" +
				"	txCoord.x=position.y; "+
				"	txCoord.y=position.x; "+
				"}";
				
		private static String fragmentShader="" +
				"uniform vec3 ambientColour;" + 
				"uniform vec3 diffuseColour;" +
				"uniform vec3 specularColour;" +
				"uniform float ambientIntensity;" +
				"uniform float diffuseIntensity;" +
				"uniform float specularIntensity;" +
				"uniform float shininess;" +
				"uniform sampler2D texture01;" + //add
				"varying vec2 txCoord;" + //add
				
				"varying float distance;" +
				"varying vec3 N;" +
				"varying vec3 V;" +
				"varying vec3 L;" +
				
				"uniform vec3 lightPosition;" +
				"void main(){" +
				"	vec3 h = normalize(V + L);" +
				"	vec3 tmpColor = max(dot(N,L), 0.0) *  diffuseColour * diffuseIntensity / distance;" +
				"	tmpColor += pow(max(dot(N,h), 0.0), shininess) * specularColour * specularIntensity / distance;" +
				"	tmpColor += ambientColour * ambientIntensity;" +
				
				"	gl_FragColor =  texture2D(texture01, txCoord) * vec4(tmpColor, 1.0);" + //modified
"}";
		
		private Shader shaderDisk;
		private Shader shaderCilinder;
		private Shader shaderSphere;

		private int textureBase;//add
		private int textureTop;
		private int textureCTop;
		private int textureSpheres;
		private int textureDisc;
		private int textureCilinder;
		private int vbo;//vertices buffer object 
		private int nbo; //normals buffer object
		private int ibo;//indices buffer object
		private int tbo;//add
		private int attribObject;
		private int attribObject2;//add
		private int normalsAttribObject;
		private int uniformTransform;
		private int uniformLightTransform;
		private int uniformTexture;//add
		
		private int lightPosition;
		private int ambientColour;
		private int diffuseColour;
		private int specularColour;
		private int ambientIntensity;
		private int diffuseIntensity;
		private int specularIntensity;
		private int shininess;

		private ModelViewMatrix matrix=new ModelViewMatrix();
		float[] lightTransformMatrix;
		
		float t=0;
		
		/* Drawing method
		 * */
		public void display(GLAutoDrawable arg0) {
			
			//Take the gl elemnt
			GL2ES2 gl=(GL2ES2)arg0.getGL();
			
			gl.getGL2ES2();
			//Clear the image
			gl.glClearColor(1, 1, 1, 1);
			
			gl.glClear(GL.GL_COLOR_BUFFER_BIT | GL.GL_DEPTH_BUFFER_BIT);
			gl.glEnable(GL.GL_DEPTH_TEST);
			
			t+=0.01f;
			float rotation=0.2f+t;
			

			matrix.loadIdentity();			
			matrix.rotateX((float)(-Math.PI/9.0f));//
			lightTransformMatrix = matrix.asOpenGLMatrix();
			
			
			matrix.rotateY(rotation);
			
			gl.glEnableVertexAttribArray(attribObject2);
			gl.glBindBuffer(GL.GL_ARRAY_BUFFER, tbo);
			
			//draws the cilinder of the base
			gl.glUseProgram(shaderCilinder.getShadingProgram());
			setUniforms(gl, textureCilinder);
			drawCube(gl,0.5f,0.09f,0.5f);/////////////////////////

			//draws the disk of the base
			matrix.translate3f(0, 0.09f, 0);
			gl.glUseProgram(shaderDisk.getShadingProgram());
			setUniforms(gl, textureDisc);
			drawCube(gl,0.5f,0.05f,0.5f);
				
			float step=(float)(2*Math.PI/NUMBER_OF_POLES);
			
			for (int i = 0; i < NUMBER_OF_POLES; i++) {
				matrix.push();
					matrix.rotateY(i*step);
					matrix.translate3f(0.4f,0.0f,0.0f);
					gl.glUseProgram(shaderCilinder.getShadingProgram());
					setUniforms(gl, textureBase);
					drawCube(gl,0.02f,0.5f,0.02f);
					gl.glUseProgram(shaderSphere.getShadingProgram());
					setUniforms(gl, textureSpheres);
					float y=(float)(0.10f*(Math.sin(rotation*2.0f+i*step)));
					matrix.translate3f(0,y + 0.23f,0);
					drawCube(gl,0.05f,0.05f,0.05f);
				matrix.pop();
			}
			
			//gl.glUseProgram(shaderCilinder.getShadingProgram());
			//setUniforms(gl, textureCilinder);
			//drawCube(gl,0.5f,0.02f,0.5f);/////////////////////////
			//gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_S, GL.GL_REPEAT);
			//gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_WRAP_T, GL.GL_REPEAT);
			//gl.glBindTexture(GL.GL_TEXTURE_2D, textureCTop);
			
			
			//draws the disk of the top
			gl.glUseProgram(shaderSphere.getShadingProgram());
			setUniforms(gl, textureCTop);
			matrix.translate3f(0.0f,0.5f,0.0f);
			matrix.scale(0.9f, 0.9f, 0.9f);
			
			drawCube(gl,0.6f,0.09f,0.6f);
			
			//matrix.translate3f(0.0f,0.5f,0.0f);
			
		}

		private void setUniforms(GL2ES2 gl, int texture) {
			gl.glUniformMatrix4fv(uniformLightTransform, 1, false, lightTransformMatrix, 0);
			gl.glUniform3f(lightPosition, 0.0f, 0.6f, -1.0f);//
			gl.glUniform3f(ambientColour, 0.8f, 0.6f, 0.6f); //m
			gl.glUniform3f(diffuseColour, 0.8f, 0.6f, 0.6f);//m
			gl.glUniform3f(specularColour, 0.8f, 0.3f, 0.0f);
			gl.glUniform1f(ambientIntensity, 0.2f);
			gl.glUniform1f(specularIntensity, 0.8f);
			gl.glUniform1f(diffuseIntensity, 0.6f);
			gl.glUniform1f(shininess, 9f);
			gl.glActiveTexture(GL.GL_TEXTURE0);
			gl.glBindTexture(GL.GL_TEXTURE_2D, texture);
			gl.glUniform1i(gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "texture01"), 0);
		}
		
		private void drawCube(GL2ES2 gl,float sizeX,float sizeY,float sizeZ) {
			
			matrix.push();
			matrix.scale(sizeX, sizeY, sizeZ);
			
			float[] mat=matrix.asOpenGLMatrix();

			gl.glUniformMatrix4fv(uniformTransform, 1, false, mat, 0);

			//added
			gl.glEnableVertexAttribArray(attribObject2);
			gl.glBindBuffer(GL.GL_ARRAY_BUFFER, tbo);
			gl.glVertexAttribPointer(attribObject2, 2, GL2ES2.GL_FLOAT, false, 0, 0); 
			//added
			
			gl.glEnableVertexAttribArray(attribObject);
			gl.glBindBuffer(GL.GL_ARRAY_BUFFER, vbo);
			gl.glVertexAttribPointer(attribObject, 2, GL2ES2.GL_FLOAT, false, 0, 0);

			gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, ibo);
			
			gl.glDrawElements(GL.GL_TRIANGLES, Carousel.GraphicsListener.indices.length, GL.GL_UNSIGNED_SHORT, 0);
			
			matrix.pop();
		}
		
      
		
		@Override
		public void dispose(GLAutoDrawable arg0) {
			// TODO Auto-generated method stub
			
		}

		/* init method
		 * */
		public void init(GLAutoDrawable arg0) {

			//Take the gl elemnt
			GL2ES2 gl=(GL2ES2)arg0.getGL();
			
			shaderSphere=new Shader(fragmentShader, vertexShaderSphere);
			shaderCilinder=new Shader(fragmentShader, vertexShaderCilinder);
			shaderDisk=new Shader(fragmentShader, vertexShaderDisk);
			
			shaderSphere.compileShader(gl);
			shaderCilinder.compileShader(gl);
			shaderDisk.compileShader(gl);

			//added
			try {
				Texture t1 = TextureIO.newTexture(new File("wood.jpg"),true);
				textureBase=t1.getTextureObject(gl);
				Texture t2 = TextureIO.newTexture(new File("steel.jpg"),true);
				textureTop=t2.getTextureObject(gl);
				Texture t3 = TextureIO.newTexture(new File("earth_sphere.jpg"),true);
				textureSpheres=t3.getTextureObject(gl);
				Texture t4 = TextureIO.newTexture(new File("color.png"),true);
				textureCilinder=t4.getTextureObject(gl);//texturecilinder
				Texture t5 = TextureIO.newTexture(new File("Parke.jpg"),true);
				textureDisc=t5.getTextureObject(gl);//textureDisc
				Texture t6 = TextureIO.newTexture(new File("Red.jpg"),true);
				textureCTop=t6.getTextureObject(gl);//texturetopcilinder
			} catch (Exception e) {
				System.out.println(e.getMessage());
			}
			//added
			
			int[] vbos = new int[4]; //modified

			gl.glGenBuffers(4, vbos, 0); //modified
			
			this.vbo=vbos[0];
			this.nbo=vbos[1];
			this.ibo=vbos[2];
			this.tbo=vbos[3]; //add
			
			//quadrato di (STPS + 1) * (STEPS + 1) campionamenti, ognuno dei quali ha due componenti
			float[] vertices = new float[2 * (STEPS + 1) * (STEPS + 1)];
			/* i e' l'altezza del quadrato, j la larghezza */
			for (int i = 0; i <= STEPS; i++) {
				for (int j = 0; j <= STEPS; j++) {
					vertices[2 * i * (STEPS + 1) + 2 * j] = j * passo; //x del punto
					vertices[2 * i * (STEPS + 1) + 2 * j + 1] = i * passo; //y del punto
				}
			}
			
			//ci sono STEPS * STEPS quadrati, per ognuno mi servono due triangoli, quindi sei vertici
			short[] indices = new short[6 * STEPS * STEPS];
			int i;
			for (i = 0; i < STEPS; i++) {
				short v1 = (short)(i * (STEPS + 1)); //possibile bug
				short v2 = (short)((STEPS + 1) * (i + 1));
				for (int j = 0; j < STEPS; j++) {
						indices[6 * STEPS * i + 6 * j] = v2;
						indices[6 * STEPS * i + 6 * j + 1] = v1;
						indices[6 * STEPS * i + 6 * j + 2] = ++v2;
						indices[6 * STEPS * i + 6 * j + 3] = v2;
						indices[6 * STEPS * i + 6 * j + 4] = v1;
						indices[6 * STEPS * i + 6 * j + 5] = ++v1;
				}
			}
			Carousel.GraphicsListener.indices = indices;
			
			ByteBuffer byteData=Buffers.loadFloatBuffer(vertices);
			gl.glBindBuffer(GL.GL_ARRAY_BUFFER, this.vbo);
			gl.glBufferData(GL.GL_ARRAY_BUFFER, vertices.length*Buffers.SIZE_OF_FLOAT, byteData, GL.GL_STATIC_DRAW);
		/*	
			ByteBuffer normalsData=Buffers.loadFloatBuffer(normals);
			gl.glBindBuffer(GL.GL_ARRAY_BUFFER, this.nbo);
			gl.glBufferData(GL.GL_ARRAY_BUFFER, normals.length*Buffers.SIZE_OF_FLOAT, normalsData, GL.GL_STATIC_DRAW);
			*/
			ByteBuffer indicesBytes=Buffers.loadShortBuffer(indices);
			gl.glBindBuffer(GL.GL_ELEMENT_ARRAY_BUFFER, this.ibo);
			gl.glBufferData(GL.GL_ELEMENT_ARRAY_BUFFER, indices.length*Buffers.SIZE_OF_SHORT, indicesBytes, GL.GL_STATIC_DRAW);
			
			//add
			//This minification filter was added just to let the texture work on my graphic card (i'm not kidding)
			gl.glTexParameteri(GL.GL_TEXTURE_2D, GL.GL_TEXTURE_MIN_FILTER, GL.GL_LINEAR);
			//add
			
			normalsAttribObject=gl.glGetAttribLocation(shaderSphere.getShadingProgram(), "normal");
			
			attribObject=gl.glGetAttribLocation(shaderSphere.getShadingProgram(), "position");
			
			//Initialize Blinn-Phong parameters
			lightPosition = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "lightPosition");
			diffuseColour = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "diffuseColour");
			specularColour = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "specularColour");
			ambientColour = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "ambientColour");
			shininess = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "shininess");
			
			ambientIntensity = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "ambientIntensity");
			diffuseIntensity = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "diffuseIntensity");
			specularIntensity = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "specularIntensity");
			
			uniformTransform = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "transform");
			uniformLightTransform = gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "lightTransform");
			
			uniformTexture=gl.glGetUniformLocation(shaderSphere.getShadingProgram(), "texture01"); //add
			
		}

		public void reshape(GLAutoDrawable arg0, int arg1, int arg2, int arg3, int arg4) {

		}
	}
	static final long serialVersionUID=100;
}
